package es.us.idinfor.rmplugin.operator;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.rapidminer.example.Attribute;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.annotation.ResourceConsumptionEstimator;
import com.rapidminer.operator.preprocessing.PreprocessingModel;
import com.rapidminer.operator.preprocessing.PreprocessingOperator;
import com.rapidminer.operator.preprocessing.discretization.AbstractDiscretizationOperator;
import com.rapidminer.operator.preprocessing.discretization.DiscretizationModel;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeBoolean;
import com.rapidminer.parameter.ParameterTypeCategory;
import com.rapidminer.parameter.ParameterTypeInt;
import com.rapidminer.parameter.conditions.BooleanParameterCondition;
import com.rapidminer.parameter.conditions.EqualTypeCondition;
import com.rapidminer.tools.OperatorResourceConsumptionHandler;

import es.us.idinfor.rmplugin.algorithm.ExtendedRange;
import es.us.idinfor.rmplugin.discretizer.CAIMDiscretizer;
import es.us.idinfor.rmplugin.statistic.Metrics;
import es.us.idinfor.rmplugin.util.Utils;

/**
 * <p>This operator discretizes feature values and gets the best intervals.<br />
 * It uses the CAIM coefficient to get a value of dependence between intervals and classes.</p>
 * 
 * <p>This operator needs a class feature to calculate the CAIM coefficient.</p>
 * 
 * @author Miguel Angel Alvarez de la Concepcion
 * @version 1.0
 */
public class CAIMDiscretization extends AbstractDiscretizationOperator {
	
	static {
		registerDiscretizationOperator(CAIMDiscretization.class);
	}
	
	/**
	 * The parameter indicates if long range names should be used.
	 */
	public static final String PARAMETER_RANGE_NAME_TYPE = "range_name_type";
	
	/**
	 * The parameter indicates if the number of digits is obtained automatically.
	 */
	public static final String PARAMETER_AUTOMATIC_NUMBER_OF_DIGITS = "automatic_number_of_digits";

	/**
	 * The parameter indicates the number of digits.
	 */
	public static final String PARAMETER_NUMBER_OF_DIGITS = "number_of_digits";
	
	/**
	 * The parameter name for the typification.
	 */
	public static final String PARAMETER_TYPIFY = "typify";
	
	/**
	 * Default constructor.
	 * 
	 * @param description The description of the operator.
	 */
	public CAIMDiscretization(OperatorDescription description) {
		super(description);
	}

	/**
	 * @see PreprocessingOperator#createPreprocessingModel(ExampleSet)
	 */
	public PreprocessingModel createPreprocessingModel(ExampleSet exampleSet) throws OperatorException {
		if(exampleSet.getAttributes().getLabel() == null) {
			logError("There is not a label attribute.");
			throw new OperatorException("CAIM Discretization operator must receive an example set with a label.");
		}
		else if(exampleSet.getAttributes().getLabel().isNumerical()) {
			logError("The label attribute is not nominal.");
			throw new OperatorException("CAIM Discretization operator must receive an example set with a nominal label.");
		}
		else {
			CAIMDiscretizer caim = new CAIMDiscretizer(exampleSet);
			DiscretizationModel model = new DiscretizationModel(exampleSet);
			Map<Attribute, double[]> ranges = new HashMap<Attribute, double[]>();
			Map<Attribute, ExtendedRange> caimRanges = new HashMap<Attribute, ExtendedRange>();
			int numClasses = Metrics.getNumberOfDifferentValues(exampleSet, exampleSet.getAttributes().getLabel());
			int numberOfDigits = -1;
			
			if(!getParameterAsBoolean(PARAMETER_AUTOMATIC_NUMBER_OF_DIGITS)) {
				numberOfDigits = getParameterAsInt(PARAMETER_NUMBER_OF_DIGITS);
			}
			
			if(getParameterAsBoolean(PARAMETER_TYPIFY)) {
				Utils.typify(exampleSet);
			}
			
			caimRanges = caim.computeValues(exampleSet.getAttributes(), numClasses);
			
			for(Entry<Attribute, ExtendedRange> e: caimRanges.entrySet()) {
				ranges.put(e.getKey(), e.getValue().getRanges());
			}
			
			model.setRanges(ranges, "range", getParameterAsInt(PARAMETER_RANGE_NAME_TYPE), numberOfDigits);
			
			caimRanges = null;
			ranges = null;
			caim = null;
			
			return model;
		}
	}

	/**
	 * @see PreprocessingOperator#getPreprocessingModelClass()
	 */
	public Class<? extends PreprocessingModel> getPreprocessingModelClass() {
		return DiscretizationModel.class;
	}
	
	/**
	 * @see PreprocessingOperator#getParameterTypes()
	 */
	public List<ParameterType> getParameterTypes() {
		List<ParameterType> types = super.getParameterTypes();
		ParameterType type = null;
		
		type = new ParameterTypeBoolean(PARAMETER_TYPIFY, "Check it if you want to typify the dataset values.", false, true);
		types.add(type);
		
		type = new ParameterTypeCategory(PARAMETER_RANGE_NAME_TYPE, "Indicates if long range names including the limits should be used.", DiscretizationModel.RANGE_NAME_TYPES, DiscretizationModel.RANGE_NAME_LONG);
		types.add(type);
		
		type = new ParameterTypeBoolean(PARAMETER_AUTOMATIC_NUMBER_OF_DIGITS, "Indicates if the number of digits should be automatically determined for the range names.", true);
		type.registerDependencyCondition(new EqualTypeCondition(this, PARAMETER_RANGE_NAME_TYPE, DiscretizationModel.RANGE_NAME_TYPES, false, DiscretizationModel.RANGE_NAME_INTERVAL));
		types.add(type);

		type = new ParameterTypeInt(PARAMETER_NUMBER_OF_DIGITS, "The minimum number of digits used for the interval names (-1: determine minimal number automatically).", -1, Integer.MAX_VALUE, -1);
		type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_AUTOMATIC_NUMBER_OF_DIGITS, false, false));
		types.add(type);
		
		type = null;
		
		return types;
	}
	
	/**
	 * @see PreprocessingOperator#isSupportingAttributeRoles()
	 */
	public boolean isSupportingAttributeRoles() {
		return true;
	}
	
	/**
	 * @see Operator#getResourceConsumptionEstimator()
	 */
	public ResourceConsumptionEstimator getResourceConsumptionEstimator() {
		return OperatorResourceConsumptionHandler.getResourceConsumptionEstimator(getInputPort(), CAIMDiscretization.class, attributeSelector);
	}
}
